#include "hi_virtualtp.h"

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>

#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/interrupt.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/version.h>


/* The TP can support 5 points, but we only use 1*/
#define TP_MAX_TOUCH_POINTS             (1)
/* screen resolution*/
#define TP_SCREEN_WIDTH_NUM             (4000)
#define TP_SCREEN_HEIGHT_NUM            (3000)
#define TP_SCREEN_STEP_VALUE            (4)

#define TP_EVENT_PRESS_DOWN             (0)
#define TP_EVENT_LIFT_UP                (1)
#define TP_EVENT_CONTACT                (2)

typedef enum
{
    VIRTUALTP_TOUCHDIRECT_UP = 0,
    VIRTUALTP_TOUCHDIRECT_DOWN,
    VIRTUALTP_TOUCHDIRECT_LEFT,
    VIRTUALTP_TOUCHDIRECT_RIGHT,
    VIRTUALTP_TOUCHDIRECT_INSITU,
    VIRTUALTP_TOUCHDIRECT_BUTT
} virtualltp_touchdirect;

typedef struct
{
    u16 x;          /*x coordinate */
    u16 y;          /*y coordinate */
    u16 pressure;
    u8  touch_event; /* touch event: 0 -- down; 1-- up; 2 -- contact */
    u8  finger_id;   /*touch ID */
    u8  point_num;
} virtualltp_tsevent;

/*start define of ft*/
static struct input_dev* g_inputdev = NULL;

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,36))
static int tp_open(struct inode* inode, struct file* filp)
#else
static int tp_open( struct file* filp)
#endif
{
    return 0;
}

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,36))
static int tp_release(struct inode* inode, struct file* filp)
#else
static int tp_release(struct file* filp)
#endif
{
    return 0;
}

static int tp_data_sync(virtualltp_tsevent* event)
{
    bool act = 0;
    struct input_dev* input = g_inputdev;
    if(event->point_num != 0) {
        input_mt_slot(input, 0);
        act = (event->touch_event == TP_EVENT_PRESS_DOWN || event->touch_event == TP_EVENT_CONTACT);
        input_mt_report_slot_state(input, MT_TOOL_FINGER, act);
        if (!act) {
            goto end;
        }
        input_report_abs(input, ABS_MT_POSITION_X, event->x);
        input_report_abs(input, ABS_MT_POSITION_Y, event->y);

    }
end:
    input_mt_sync_frame(input);
    input_sync(input);
    return 0;
}

static bool tp_check_spot(const hi_virtualtp_twospot* tp_twospot)
{
    if (tp_twospot == NULL) {
        printk("the input spot is null. \r\n");
        return -1;
    }

    if (tp_twospot->firstspot.pointstate == HI_VIRTUALTP_POINTSTATE_UP) {
        printk("the firt input spot state can not be up. \r\n");
        return -1;
    }

    if (tp_twospot->firstspot.point.x > TP_SCREEN_WIDTH_NUM || tp_twospot->firstspot.point.y > TP_SCREEN_HEIGHT_NUM
        || tp_twospot->secondspot.point.x > TP_SCREEN_WIDTH_NUM || tp_twospot->secondspot.point.y > TP_SCREEN_HEIGHT_NUM) {
        printk("the input spot size is out of range. \r\n");
        return -1;
    }

    if ((tp_twospot->firstspot.point.x != tp_twospot->secondspot.point.x) &&
         (tp_twospot->firstspot.point.y != tp_twospot->secondspot.point.y)) {
        printk("the line only support horizontal or vertical. \r\n");
        return -1;
    }

    return 0;
}

static int tp_find_touchdirect(const hi_virtualtp_twospot* tp_twospot, virtualltp_touchdirect* touch_direct)
{
    if((tp_twospot->firstspot.point.x == tp_twospot->secondspot.point.x) &&
        (tp_twospot->firstspot.point.y == tp_twospot->secondspot.point.y)) {
        *touch_direct = VIRTUALTP_TOUCHDIRECT_INSITU;
    }
    else if ((tp_twospot->firstspot.point.x == tp_twospot->secondspot.point.x) &&
        (tp_twospot->firstspot.point.y < tp_twospot->secondspot.point.y)) {
        *touch_direct = VIRTUALTP_TOUCHDIRECT_UP;
    }
    else if ((tp_twospot->firstspot.point.x == tp_twospot->secondspot.point.x) &&
        (tp_twospot->firstspot.point.y > tp_twospot->secondspot.point.y)) {
        *touch_direct = VIRTUALTP_TOUCHDIRECT_DOWN;
    }
    else if ((tp_twospot->firstspot.point.x < tp_twospot->secondspot.point.x) &&
        (tp_twospot->firstspot.point.y == tp_twospot->secondspot.point.y)) {
        *touch_direct = VIRTUALTP_TOUCHDIRECT_RIGHT;
    }
    else if ((tp_twospot->firstspot.point.x > tp_twospot->secondspot.point.x) &&
        (tp_twospot->firstspot.point.y == tp_twospot->secondspot.point.y)) {
        *touch_direct = VIRTUALTP_TOUCHDIRECT_LEFT;
    }
    else {
        printk("Not support the point set. \r\n");
        return -1;
    }

    return 0;
}

static int tp_fill_event(const hi_virtualtp_touchspot* touch_spot)
{
    virtualltp_tsevent event = {0};
    int ret = -1;
    int fingernum = 1;
    event.x = touch_spot->point.x;
    event.y = touch_spot->point.y;
    event.point_num = fingernum;
    if (touch_spot->pointstate == HI_VIRTUALTP_POINTSTATE_UP) {
        event.touch_event =
            TP_EVENT_LIFT_UP;
    } else {
        event.touch_event =
            TP_EVENT_CONTACT;
    }
    event.finger_id = 0;
    ret = tp_data_sync(&event);
    if (ret == -1) {
        return -1;
    }
    msleep(3);
    return 0;
}

static int tp_set_direct_up(const hi_virtualtp_twospot* tp_twospot)
{
    hi_virtualtp_touchspot interval_spot = {0};
    int count = 0;
    int i = 0;
    count = (tp_twospot->secondspot.point.y - tp_twospot->firstspot.point.y) / TP_SCREEN_STEP_VALUE;
    if (count == 0) {
        tp_fill_event(&tp_twospot->firstspot);
        tp_fill_event(&tp_twospot->secondspot);
    } else if (count > 0) {
        for (i = 0; i <= count; i++) {
            interval_spot.point.x = tp_twospot->firstspot.point.x;
            interval_spot.point.y = tp_twospot->firstspot.point.y + TP_SCREEN_STEP_VALUE * i;
            if (interval_spot.point.y != tp_twospot->secondspot.point.y) {
                interval_spot.pointstate = tp_twospot->firstspot.pointstate;
            } else {
                interval_spot.pointstate = tp_twospot->secondspot.pointstate;
            }
            tp_fill_event(&interval_spot);
        }
        if (tp_twospot->firstspot.point.y + TP_SCREEN_STEP_VALUE * count != tp_twospot->secondspot.point.y) {
            tp_fill_event(&tp_twospot->secondspot);
        }
    }
    return 0;
}

static int tp_set_direct_down(const hi_virtualtp_twospot* tp_twospot)
{
    hi_virtualtp_touchspot interval_spot = {0};
    int count = 0;
    int i = 0;
    count = (tp_twospot->firstspot.point.y - tp_twospot->secondspot.point.y) / TP_SCREEN_STEP_VALUE;
    if (count == 0) {
        tp_fill_event(&tp_twospot->firstspot);
        tp_fill_event(&tp_twospot->secondspot);
    } else if (count > 0) {
        for (i = 0; i <= count; i++) {
            interval_spot.point.x = tp_twospot->firstspot.point.x;
            interval_spot.point.y = tp_twospot->firstspot.point.y - TP_SCREEN_STEP_VALUE * i;
            if (interval_spot.point.y != tp_twospot->secondspot.point.y) {
                interval_spot.pointstate = tp_twospot->firstspot.pointstate;
            } else {
                interval_spot.pointstate = tp_twospot->secondspot.pointstate;
            }
            tp_fill_event(&interval_spot);
        }
        if (tp_twospot->firstspot.point.y - TP_SCREEN_STEP_VALUE * count != tp_twospot->secondspot.point.y) {
            tp_fill_event(&tp_twospot->secondspot);
        }
    }
    return 0;
}

static int tp_set_direct_right(const hi_virtualtp_twospot* tp_twospot)
{
    hi_virtualtp_touchspot interval_spot = {0};
    int count = 0;
    int i = 0;
    count = (tp_twospot->secondspot.point.x - tp_twospot->firstspot.point.x) / TP_SCREEN_STEP_VALUE;
    if (count == 0) {
        tp_fill_event(&tp_twospot->firstspot);
        tp_fill_event(&tp_twospot->secondspot);
    } else if (count > 0) {
        for (i = 0; i <= count; i++) {
            interval_spot.point.y = tp_twospot->firstspot.point.y;
            interval_spot.point.x = tp_twospot->firstspot.point.x + TP_SCREEN_STEP_VALUE * i;
            if (interval_spot.point.x != tp_twospot->secondspot.point.x) {
                interval_spot.pointstate = tp_twospot->firstspot.pointstate;
            } else {
                interval_spot.pointstate = tp_twospot->secondspot.pointstate;
            }
            tp_fill_event(&interval_spot);
        }
        if (tp_twospot->firstspot.point.x + TP_SCREEN_STEP_VALUE * count != tp_twospot->secondspot.point.x) {
            tp_fill_event(&tp_twospot->secondspot);
        }
    }
    return 0;
}

static int tp_set_direct_left(const hi_virtualtp_twospot* tp_twospot)
{
    hi_virtualtp_touchspot interval_spot = {0};
    int count = 0;
    int i = 0;
    count = (tp_twospot->firstspot.point.x - tp_twospot->secondspot.point.x) / TP_SCREEN_STEP_VALUE;
    if (count == 0) {
        tp_fill_event(&tp_twospot->firstspot);
        tp_fill_event(&tp_twospot->secondspot);
    } else if (count > 0) {
        for (i = 0; i <= count; i++) {
            interval_spot.point.y = tp_twospot->firstspot.point.y;
            interval_spot.point.x = tp_twospot->firstspot.point.x - TP_SCREEN_STEP_VALUE * i;
            if (interval_spot.point.x != tp_twospot->secondspot.point.x) {
                interval_spot.pointstate = tp_twospot->firstspot.pointstate;
            } else {
                interval_spot.pointstate = tp_twospot->secondspot.pointstate;
            }
            tp_fill_event(&interval_spot);
        }
        if (tp_twospot->firstspot.point.x - TP_SCREEN_STEP_VALUE * count != tp_twospot->secondspot.point.x) {
            tp_fill_event(&tp_twospot->secondspot);
        }
    }
    return 0;
}


static int tp_set_touch(const hi_virtualtp_twospot* tp_twospot)
{
    int ret = -1;
    virtualltp_touchdirect touch_direct = VIRTUALTP_TOUCHDIRECT_BUTT;
    if (tp_check_spot(tp_twospot)) {
        return -1;
    }

    ret = tp_find_touchdirect(tp_twospot, &touch_direct);
    if (ret) {
        return -1;
    }

    switch(touch_direct) {
        case VIRTUALTP_TOUCHDIRECT_INSITU:
            tp_fill_event(&tp_twospot->firstspot);
            tp_fill_event(&tp_twospot->secondspot);
            break;
        case VIRTUALTP_TOUCHDIRECT_UP:
            tp_set_direct_up(tp_twospot);
            break;
        case VIRTUALTP_TOUCHDIRECT_DOWN:
            tp_set_direct_down(tp_twospot);
            break;
        case VIRTUALTP_TOUCHDIRECT_RIGHT:
            tp_set_direct_right(tp_twospot);
            break;
        case VIRTUALTP_TOUCHDIRECT_LEFT:
            tp_set_direct_left(tp_twospot);
            break;
        default:
            printk("Error touch direct. \r\n");
            break;
    }

    return 0;
}

static int tp_devinput_init(void)
{
    int error = 0;
    g_inputdev = input_allocate_device();
    if (g_inputdev == NULL) {
        printk(" func:%s line:%d \r\n", __FUNCTION__, __LINE__);
        return -1;
    }
    g_inputdev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
    set_bit(EV_SYN, g_inputdev->evbit);
    set_bit(EV_KEY, g_inputdev->evbit);
    set_bit(EV_ABS, g_inputdev->evbit);
    set_bit(INPUT_PROP_DIRECT, g_inputdev->propbit);
    input_set_abs_params(g_inputdev, ABS_MT_POSITION_X, 0 , TP_SCREEN_WIDTH_NUM, 0, 0);
    input_set_abs_params(g_inputdev, ABS_MT_POSITION_Y, 0 , TP_SCREEN_HEIGHT_NUM, 0, 0);
    input_set_abs_params(g_inputdev, ABS_MT_TOUCH_MAJOR,
                         0, 0xff, 0, 0);
    input_set_abs_params(g_inputdev, ABS_MT_PRESSURE,
                         0, 0xff, 0, 0);
    input_set_abs_params(g_inputdev, ABS_MT_TRACKING_ID,
                         0, 0xff, 0, 0);

    error = input_mt_init_slots(g_inputdev, TP_MAX_TOUCH_POINTS,
                                INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);

    if (error) {
        return error;
    }
    g_inputdev->name = "ft";
    g_inputdev->id.bustype = BUS_I2C;

    error = input_register_device(g_inputdev);
    if (error) {
        dev_err(NULL, "failed to register input device: %d\n", error);
        return error;
    }

    return 0;
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36))
static int tp_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg)
#else
static long tp_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
#endif
{
    hi_virtualtp_twospot tp_twospot;
    int ret = 0;

    switch (_IOC_NR(cmd)) {
        case _IOC_NR(VIRTUALTP_SET_COORDINATE):
            ret = copy_from_user(&tp_twospot, (hi_virtualtp_twospot*)arg, sizeof(hi_virtualtp_twospot));
            if (ret != 0) {
                printk("copy_from_user error!\n");
                return -1;
            }
            ret = tp_set_touch(&tp_twospot);
            if (0 != ret) {
                printk("tp_set_touch error!\n");
                return -1;
            }
            break;
        default:
            return -1;
    }
    return 0;
}

static struct file_operations tp_fops =
{
    .owner = THIS_MODULE,
    .open  = tp_open,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36))
    .ioctl = tp_ioctl,
#else
    .unlocked_ioctl = tp_ioctl,
#endif
    .release = tp_release,
};

static struct miscdevice tp_dev =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "hi_virtualtp",
    .fops  = &tp_fops,
};


static int __init hi_tp_init(void)
{
    int ret = 0;
    ret = misc_register(&tp_dev);
    if (ret) {
        printk(KERN_ERR "register misc dev for virtual tp fail!\n");
        goto error_end;
    }

    ret = tp_devinput_init();
    if (ret) {
        dev_err(NULL, " devinput_init fail!\n");
        goto error_end;
    }

    return 0;
error_end:
    return -1;
}

static void __exit hi_tp_exit(void)
{

    printk("unregister virtual tp ko\n");
    misc_deregister(&tp_dev);
    input_unregister_device(g_inputdev);
    input_free_device(g_inputdev);
    return;
}

module_init(hi_tp_init);
module_exit(hi_tp_exit);
